/** @file
  Implementation of USB device mode APIs.

@copyright
  INTEL CONFIDENTIAL
  Copyright 2015 - 2019 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification Reference:
**/

#include <Base.h>
#include <Library/BaseLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/IoLib.h>
#include "XdciUtility.h"
#include "UsbDeviceMode.h"
#include "UsbDeviceDxe.h"

//
// Global USBD driver object. This is the main private driver object
// that contains all data needed for this driver to operate.
//
USB_DEVICE_DRIVER_OBJ mDrvObj;

//
// Global data IO transaction request object
//
USB_DEVICE_IO_REQ  mCtrlIoReq = {
  //
  // IO information containing the buffer and data size
  //
  {
    NULL,
    0,
  },
  //
  // Note: This object is used for Control Ep transfers only
  // therefore the endpoint info must always be NULL
  //
  {
    NULL,
    NULL,
  }
};

/**
  This function can be called to poll for certain value within a time given.

  @param[in] MmioAddress          The Mmio Address.
  @param[in] BitMask              Bits to be masked.
  @param[in] BitValue             Value to be polled.
  #param[in] DelayTime            Delay time in terms of 100 micro seconds.

  @retval EFI_SUCCESS             Successfully polled the value.
  @retval EFI_TIMEOUT             Timeout while polling the value.
**/
EFI_STATUS
EFIAPI
PchMmioPoll (
  IN  UINTN         MmioAddress,
  IN  UINT32        BitMask,
  IN  UINT32        BitValue,
  IN  UINT16        DelayTime
  )
{
  UINT32  LoopTime;
  UINT8   PollSuccess;

  LoopTime    = 0;
  PollSuccess = 0;

  DEBUG ((DEBUG_INFO, "PchMmioPoll32, BitMask: %x, BitValue: %x\n",  BitMask, BitValue));
  for (LoopTime = 0; LoopTime < DelayTime; LoopTime++) {
    if ((MmioRead32 (MmioAddress) & BitMask) == (BitValue & BitMask)) {
      PollSuccess = 1;
      break;
    } else {
      gBS->Stall (100);
    }
  }

  if (PollSuccess) {
    return EFI_SUCCESS;
  } else {
    return EFI_TIMEOUT;
  }
}

//
// global flag to signal device event processing loop to run/stop
//
BOOLEAN mXdciRun = FALSE;

STATIC VOID
XhciSwitchSwid (
  IN  BOOLEAN Enable
  )
{
  UINT64      XhciPciMmBase;
  UINTN       XhciMmioBase;
  UINT32      dual_role_cfg0;
  UINT32      BitValue;
  UINT32      BitMask;
  EFI_STATUS  Status;

    XhciPciMmBase = PCI_SEGMENT_LIB_ADDRESS (
                    0,
                    DEFAULT_PCI_BUS_NUMBER_PCH,
                    PCI_DEVICE_NUMBER_PCH_XHCI,
                    PCI_FUNCTION_NUMBER_PCH_XHCI,
                    0
                    );
  XhciMmioBase = PciSegmentRead32 (XhciPciMmBase + R_PCH_XHCI_MEM_BASE) & B_PCH_XHCI_MEM_BASE_BA;
  
  DEBUG ((DEBUG_INFO, "XhciPciMmBase=%x, XhciMmioBase=%x\n", XhciPciMmBase, XhciMmioBase));

  dual_role_cfg0 = MmioRead32 ((UINTN)(XhciMmioBase + 0x80D8));
  if (Enable) {
    dual_role_cfg0 = dual_role_cfg0 | (1 << 24) | (1 << 21) | (1 << 20);
    DEBUG ((DEBUG_INFO, "dual_role_cfg0 : Set SW ID : 0x%x \n", dual_role_cfg0));
  }
  else {
    dual_role_cfg0 = dual_role_cfg0 & ~(1 << 24) & ~(1 << 21) & ~(1 << 20);
    DEBUG ((DEBUG_INFO, "dual_role_cfg0 : Clear SW ID : 0x%x \n", dual_role_cfg0));
  }
  MmioWrite32 ((UINTN)(XhciMmioBase + 0x80D8), dual_role_cfg0);

  BitMask  = (UINT32) (0x20000000);
  BitValue = (UINT32) (1 << 29);
  Status = PchMmioPoll (
            (UINTN) (XhciMmioBase + 0x80DC),
            BitMask,
            ~BitValue,
            1000
            );
  if (Status == EFI_TIMEOUT) {
    DEBUG ((DEBUG_ERROR, "Timeout while polling on xHCI BAR + 0x80DC [29] for 0b, %x\n", MmioRead32((UINT32)(XhciMmioBase + 0x80DC))));
  }
}

VOID
EFIAPI
UsbdMonitorEvents (
  IN EFI_EVENT            Event,
  IN VOID                 *Context
  )
{
  USB_XDCI_DEV_CONTEXT    *XdciDevContext;
  UINT32                  EventCount;
  UINT32                  PreEventCount;
  UINT32                  LoopCount;

  XdciDevContext = (USB_XDCI_DEV_CONTEXT *) Context;
  EventCount = usb_reg_read ((UINT32)XdciDevContext->XdciMmioBarAddr, DWC_XDCI_EVNTCOUNT_REG (0));
  if (EventCount == 0) {
    return;
  }

  LoopCount = 0;
  PreEventCount = EventCount;
  while (EventCount != 0) {
    if (usb_device_isr_routine_timer_based (mDrvObj.XdciDrvObj) != EFI_SUCCESS) {
      DEBUG ((DEBUG_INFO, "UsbDeviceRun() - Failed to execute event ISR\n"));
    }
    EventCount = usb_reg_read ((UINT32)XdciDevContext->XdciMmioBarAddr, DWC_XDCI_EVNTCOUNT_REG (0));
    if (PreEventCount == EventCount) {
      LoopCount++;
      if (LoopCount >= 5) {
        DEBUG ((DEBUG_INFO, "USB is working on a long event...\n"));
        break;
      }
    } else {
      LoopCount = 0;
    }
  }

  return;
}

/**
  Initializes the XDCI core

  @param MmioBar       Address of MMIO BAR
  @param XdciHndl      Double pointer to for XDCI layer to set as an
                       opaque handle to the driver to be used in subsequent
                       interactions with the XDCI layer.

  @return EFI_SUCCESS if successfully initialized XDCI, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdInit (
  IN UINT32    MmioBar,
  IN VOID      **XdciHndl
  )
{
  EFI_STATUS               Status = EFI_DEVICE_ERROR;
  USB_DEV_CONFIG_PARAMS    ConfigParams;

  XhciSwitchSwid(TRUE);

  DEBUG ((DEBUG_INFO, "UsbdInit start\n"));
  ConfigParams.ControllerId = USB_ID_DWC_XDCI;
  ConfigParams.BaseAddress  = MmioBar;
  ConfigParams.Role = USB_ROLE_DEVICE;
  ConfigParams.Speed = USB_SPEED_SUPER;

  Status = usb_device_init (&ConfigParams, XdciHndl);

  DEBUG ((DEBUG_INFO, "UsbdInit status is %x\n", Status));
  DEBUG ((DEBUG_INFO, "ConfigParams.BaseAddress 0x%x\n", ConfigParams.BaseAddress));

  return Status;
}


/**
  Copies relevant endpoint data from standard USB endpoint descriptors
  to the usb_ep_info structure used by the XDCI

  @param EpDest   destination structure
  @param EpSrc    source structure

  @return VOID

**/
VOID
UsbdSetEpInfo (
  IN USB_EP_INFO                 *EpDest,
  IN USB_DEVICE_ENDPOINT_INFO    *EpSrc
  )
{
  EFI_USB_ENDPOINT_DESCRIPTOR              *EpDesc = NULL;
  EFI_USB_ENDPOINT_COMPANION_DESCRIPTOR    *EpCompDesc = NULL;

  /* start by clearing all data in the destination */
  SetMem (EpDest, sizeof(USB_EP_INFO), 0);
  EpDesc = EpSrc->EndpointDesc;
  EpCompDesc = EpSrc->EndpointCompDesc;

  if (EpDesc != NULL) {
    EpDest->ep_num = EpDesc->EndpointAddress & 0x0F; /* Bits 0-3 are ep num */
    EpDest->ep_dir = ((EpDesc->EndpointAddress & USB_ENDPOINT_DIR_IN) > 0) ? UsbEpDirIn : UsbEpDirOut;
    EpDest->ep_type = EpDesc->Attributes & USB_ENDPOINT_TYPE_MASK;
    EpDest->max_pkt_size = EpDesc->MaxPacketSize;
    EpDest->interval = EpDesc->Interval;
  }
  if (EpCompDesc != NULL) {
    EpDest->max_streams = EpCompDesc->Attributes & USB_EP_BULK_BM_ATTR_MASK;
    EpDest->burst_size = EpCompDesc->MaxBurst;
    EpDest->mult = EpCompDesc->BytesPerInterval;
  }

  return;
}


/**
  Initializes the given endpoint

  @param XdciHndl  Pointer (handle) to the XDCI driver object
  @param DevEpInfo Pointer to endpoint info structure
                   for the endpoint to initialize

  @return EFI_SUCCESS if operation succeeded, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdInitEp (
  IN VOID                      *XdciHndl,
  IN USB_DEVICE_ENDPOINT_INFO  *DevEpInfo
  )
{
  EFI_STATUS   Status = EFI_DEVICE_ERROR;
  USB_EP_INFO  EpInfo;

  UsbdSetEpInfo (&EpInfo, DevEpInfo);
  Status = usb_device_init_ep (XdciHndl, &EpInfo);

  return Status;
}


/**
  Callback handler used when transfer operations complete. Calls
  upper layer routine to handle the operation.

  @param XdciHndl  Pointer (handle) to the XDCI driver object
  @param XferReq   Pointer to the transfer request structure

  @return VOID

**/
VOID
EFIAPI
UsbdXferDoneHndlr (
  IN VOID                    *XdciHndl,
  IN USB_XFER_REQUEST        *XferReq
  )
{
  EFI_USB_DEVICE_XFER_INFO  XferInfo;

  DEBUG ((DEBUG_INFO, "UsbdXferDoneHndlr\n"));

  XferInfo.EndpointNum = (UINT8)XferReq->ep_info.ep_num;
  XferInfo.EndpointDir = XferReq->ep_info.ep_dir;
  XferInfo.EndpointType = XferReq->ep_info.ep_type;
  XferInfo.Buffer = XferReq->xfer_buffer;
  XferInfo.Length = XferReq->actual_xfer_len;

  //
  // If this is a non-control transfer complete, notify the class driver
  //
  if (XferInfo.EndpointNum > 0) {
    if (mDrvObj.UsbdDevObj->DataCallback != NULL) {
      mDrvObj.UsbdDevObj->DataCallback (&XferInfo);
    }
  }

  return;
}


/**
  Queue a request to transmit data

  @param XdciHndl  Pointer (handle) to the XDCI driver object
  @param IoReq     Pointer to IO structure containing details of the
                   transfer request

  @return EFI_SUCCESS if operation succeeded, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdEpTxData (
  IN VOID               *XdciHndl,
  IN USB_DEVICE_IO_REQ  *IoReq
  )
{
  EFI_STATUS        Status = EFI_DEVICE_ERROR;
  USB_XFER_REQUEST  TxReq;

  /* set endpoint data */
  UsbdSetEpInfo (&(TxReq.ep_info), &(IoReq->EndpointInfo)); /* set endpoint data */

  /* if this is a control endpoint, set the number and direction */
  if (IoReq->EndpointInfo.EndpointDesc == NULL) {
    TxReq.ep_info.ep_num = 0;
    TxReq.ep_info.ep_dir = UsbEpDirIn;
  }

  /* setup the trasfer request */
  TxReq.xfer_buffer = IoReq->IoInfo.Buffer;
  TxReq.xfer_len = IoReq->IoInfo.Length;
  TxReq.xfer_done = UsbdXferDoneHndlr;

  DEBUG ((DEBUG_INFO,  "TX REQUEST: epNum: 0x%x, epDir: 0x%x, epType: 0x%x, MaxPktSize: 0x%x\n",\
          TxReq.ep_info.ep_num, TxReq.ep_info.ep_dir, TxReq.ep_info.ep_type, TxReq.ep_info.max_pkt_size));

  Status = usb_device_ep_tx_data (XdciHndl, &TxReq);

  return Status;
}


/**
  Queue a request to receive data

  @param XdciHndl  Pointer (handle) to the XDCI driver object
  @param IoReq     Pointer to IO structure containing details of the
                   receive request

  @return EFI_SUCCESS if operation succeeded, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdEpRxData (
  IN VOID               *XdciHndl,
  IN USB_DEVICE_IO_REQ  *IoReq
  )
{
  EFI_STATUS        Status = EFI_DEVICE_ERROR;
  USB_XFER_REQUEST  RxReq;
  UINT32            ReqPacket;

  DEBUG ((DEBUG_INFO,  "RX REQUEST in: IoReq->IoInfo.Length: 0x%x\n", IoReq->IoInfo.Length));
  DEBUG ((DEBUG_INFO,  "RX REQUEST in: MaxPacketSize: 0x%x\n", IoReq->EndpointInfo.EndpointDesc->MaxPacketSize));

  if (IoReq->EndpointInfo.EndpointDesc->MaxPacketSize == 0) {
    return EFI_DEVICE_ERROR;
  }

  /* set endpoint data */
  UsbdSetEpInfo (&(RxReq.ep_info), &(IoReq->EndpointInfo));

  /* setup the trasfer request */
  RxReq.xfer_buffer = IoReq->IoInfo.Buffer;

  //
  // Transfer length should be multiple of USB packet size.
  //
  ReqPacket = IoReq->IoInfo.Length / IoReq->EndpointInfo.EndpointDesc->MaxPacketSize;
  ReqPacket = ((IoReq->IoInfo.Length % IoReq->EndpointInfo.EndpointDesc->MaxPacketSize) == 0)? ReqPacket : ReqPacket + 1;
  RxReq.xfer_len = ReqPacket * IoReq->EndpointInfo.EndpointDesc->MaxPacketSize;

  RxReq.xfer_done = UsbdXferDoneHndlr;

  DEBUG ((DEBUG_INFO,  "RX REQUEST: epNum: 0x%x, epDir: 0x%x, epType: 0x%x\n",\
          RxReq.ep_info.ep_num, RxReq.ep_info.ep_dir, RxReq.ep_info.ep_type));
  DEBUG ((DEBUG_INFO,  "RX REQUEST send: xfer_len: 0x%x\n", RxReq.xfer_len));

  Status = usb_device_ep_rx_data (XdciHndl, &RxReq);

  return Status;
}


/**
  Callback used to handle Reset events from the XDCI

  @param Param Pointer to a generic callback parameter structure

  @return XDCI usb status

**/
EFI_STATUS
EFIAPI
UsbdResetEvtHndlr (
  IN USB_DEVICE_CALLBACK_PARAM  *Param
  )
{
  EFI_STATUS    Status = EFI_DEVICE_ERROR;

  DEBUG ((DEBUG_INFO, "UsbdResetEvtHndlr\n"));

  /* reset device address to 0 */
  Status = usb_device_set_address (mDrvObj.XdciDrvObj, 0x0);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "UsbdResetHdlr() - Failed to set address in XDCI\n"));
  }

  return Status;
}


/**
  Callback used to handle Connection done events from the XDCI

  @param Param Pointer to a generic callback parameter structure

  @return XDCI usb status

**/
EFI_STATUS
EFIAPI
UsbdConnDoneEvtHndlr (
  IN USB_DEVICE_CALLBACK_PARAM *Param
  )
{
  EFI_STATUS    Status = EFI_DEVICE_ERROR;

  DEBUG ((DEBUG_INFO, "UsbdConnDoneEvtHndlr\n"));

  /* reset device address to 0 */
  Status = usb_device_set_address (mDrvObj.XdciDrvObj, 0x0);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "UsbdConnDoneHdlr() - Failed to set address in XDCI\n"));
  }

  /* set the device state to attached/connected */
  mDrvObj.State = UsbDevStateAttached;

  return Status;
}


/**
  Callback used to handle Control Endpoint Setup events from the XDCI

  @param Param Pointer to a generic callback parameter structure

  @return XDCI usb status

**/
EFI_STATUS
EFIAPI
UsbdSetupEvtHndlr (
  IN USB_DEVICE_CALLBACK_PARAM *Param
  )
{
  EFI_STATUS              Status = EFI_SUCCESS;
  EFI_USB_DEVICE_REQUEST  Req;

  DEBUG ((DEBUG_INFO, "UsbdSetupEvtHndlr\n"));

  /* Fill out request object from the incomming buffer */
  CopyMem (&Req, Param->buffer, sizeof(EFI_USB_DEVICE_REQUEST));

  Status = UsbdSetupHdlr (&Req);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "UsbdSetupEvtHndlr: ERROR status %r\n", Status));
  }

  return Status;
}


/**
 * Callback used to handle XferNotReady events from the XDCI
 *
 * @param Param Pointer to a generic callback parameter structure
 *
 * @return XDCI usb status
 */
EFI_STATUS
EFIAPI
UsbdNrdyEvtHndlr (
  IN USB_DEVICE_CALLBACK_PARAM *Param
  )
{
  DEBUG ((DEBUG_INFO, "UsbdNrdyEvtHndlr\n"));
  return EFI_SUCCESS;
}


/**
  Registers callbacks for event handlers with the XDCI layer.
  The functions will be called as the registered events are triggered.

  @param  XdciHndl to XDCI core driver
  @return EFI_SUCCESS if successful, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdRegisterCallbacks (
  IN VOID  *XdciHndl
  )
{
  if (usb_device_register_callback (XdciHndl, USB_DEVICE_RESET_EVENT, UsbdResetEvtHndlr) != EFI_SUCCESS) {
    goto UdciRegCallbackError;
  }

  if (usb_device_register_callback (XdciHndl, USB_DEVICE_CONNECTION_DONE, UsbdConnDoneEvtHndlr) != EFI_SUCCESS) {
    goto UdciRegCallbackError;
  }

  if (usb_device_register_callback (XdciHndl, USB_DEVICE_SETUP_PKT_RECEIVED, UsbdSetupEvtHndlr) != EFI_SUCCESS) {
    goto UdciRegCallbackError;
  }

  if (usb_device_register_callback (XdciHndl, USB_DEVICE_XFER_NRDY, UsbdNrdyEvtHndlr) != EFI_SUCCESS) {
    goto UdciRegCallbackError;
  }

  return EFI_SUCCESS;

UdciRegCallbackError:
  return EFI_DEVICE_ERROR;
}


/**
  Returns the configuration descriptor for this device. The data
  buffer returned will also contain all downstream interface and
  endpoint buffers.

  @param Buffer    Pointer to destination buffer to copy descriptor data to
  @param DescIndex the index of the descriptor to return
  @param ReqLen    the length in bytes of the request buffer
  @param DataLen   Pointer whos value is to be filled with the byte count of
                   data copied to the output buffer

  @return EFI_SUCCESS if descritor successfully copied, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdGetConfigDesc (
  IN VOID      *Buffer,
  IN UINT8     DescIndex,
  IN UINT32    ReqLen,
  IN UINT32    *DataLen
  )
{
  EFI_STATUS             Status = EFI_DEVICE_ERROR;
  UINT8                  NumConfigs = 0;
  UINT32                 ConfigLen = 0;
  USB_DEVICE_CONFIG_OBJ  *ConfigObj = NULL;
  VOID                   *Descriptor = 0;
  UINT32                 Length = 0;

  DEBUG ((DEBUG_INFO, "UsbdGetConfigDesc()\n"));

  /*
   * For a CONFIGURATION request we send back all descriptors branching out
   * from this descriptor including the INTERFACE and ENDPOINT descriptors
   */

  /* Verify the requested configuration exists - check valid index */
  NumConfigs = mDrvObj.UsbdDevObj->DeviceDesc->NumConfigurations;

  if (DescIndex < NumConfigs) {
    /* get the configuration object using the index offset */
    ConfigObj = (mDrvObj.UsbdDevObj->ConfigObjs + DescIndex);
    /* get the complete configuration buffer block including Interface and Endpoint data */
    Descriptor = ConfigObj->ConfigAll;
    /* The config descriptor TotalLength has the full value for all desc buffers */
    ConfigLen = ConfigObj->ConfigDesc->TotalLength;
    /* copy the data to the output buffer */
    Length = MIN (ReqLen, ConfigLen);
    CopyMem (Buffer, Descriptor, Length);
    *DataLen = Length;
    Status = EFI_SUCCESS;
  } else {
    DEBUG ((DEBUG_INFO, "UsbdGetConfigDesc() - Invalid Config index: %i\n", DescIndex));
  }

  if (Status == EFI_SUCCESS) {
    if (ConfigObj != NULL) {
      PrintConfigDescriptor (ConfigObj->ConfigDesc);
    }
  }

  return Status;
}


/**
  Sets the active configuration to the selected configuration index if it exists

  @param CfgValue  the configuration value to set

  @return EFI_SUCCESS if the configuration was set, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdSetConfig (
  UINT8  CfgValue
  )
{
  EFI_STATUS                 Status = EFI_DEVICE_ERROR;
  UINT8                      numConfigs = 0;
  USB_DEVICE_CONFIG_OBJ      *pConfigObj = NULL;
  USB_DEVICE_INTERFACE_OBJ   *pIfObj = NULL;
  USB_DEVICE_ENDPOINT_OBJ    *pEpObj = NULL;
  UINT8                      cfgItr = 0;
  UINT8                      ifItr = 0;
  UINT8                      epItr = 0;
  USB_DEVICE_ENDPOINT_INFO   epInfo;
  USB_EP_INFO                UsbEpInfo;

  DEBUG ((DEBUG_INFO, "UsbdSetConfig()\n"));

  /* Verify the requested configuration exists - check valid index */
  numConfigs = mDrvObj.UsbdDevObj->DeviceDesc->NumConfigurations;

  if (CfgValue != 0) {
    /* Search for a matching configuration */
    for (cfgItr = 0; cfgItr < numConfigs; cfgItr++) {
      pConfigObj = (mDrvObj.UsbdDevObj->ConfigObjs + cfgItr);
      if (pConfigObj->ConfigDesc->ConfigurationValue == CfgValue) {

        /* Set the active configuration object */
        mDrvObj.ActiveConfigObj = pConfigObj;

        /* Find all interface objects for this configuration */
        for (ifItr = 0; ifItr < pConfigObj->ConfigDesc->NumInterfaces; ifItr++) {
          pIfObj = (pConfigObj->InterfaceObjs + ifItr);

          /* Configure the Endpoints in the XDCI */
          for (epItr = 0; epItr < pIfObj->InterfaceDesc->NumEndpoints; epItr++) {
            pEpObj = (pIfObj->EndpointObjs + epItr);

            epInfo.EndpointDesc = pEpObj->EndpointDesc;
            epInfo.EndpointCompDesc = pEpObj->EndpointCompDesc;

            if (UsbdInitEp (mDrvObj.XdciDrvObj, &epInfo) == EFI_SUCCESS) {
              UsbdSetEpInfo(&UsbEpInfo, &epInfo);
              if (usb_device_ep_enable (mDrvObj.XdciDrvObj, &UsbEpInfo) == EFI_SUCCESS) {
                Status = EFI_SUCCESS;
              } else {
                DEBUG ((DEBUG_INFO, "UsbdSetConfig() - Failed to enable endpoint\n"));
              }
            } else {
              DEBUG ((DEBUG_INFO, "UsbdSetConfig() - Failed to initialize endpoint\n"));
            }
          }
        }

        /* Let the class driver know it is configured */
        if (Status == EFI_SUCCESS) {
          if (mDrvObj.UsbdDevObj->ConfigCallback != NULL) {
            mDrvObj.UsbdDevObj->ConfigCallback (CfgValue);
          }
        }

        mDrvObj.State = UsbDevStateConfigured; /* we are now configured */

        break; /* break from config search loop */
      }
    }
  }

  if (EFI_ERROR (Status)) {
   DEBUG ((DEBUG_INFO, "UsbdSetConfig() - Invalid requested configuration value: %i\n", CfgValue));
  }

  return Status;
}


/**
  Returns the currently active configuration value

  @param Buffer    Pointer to destination buffer to copy configuration value to
  @param ReqLen    the length in bytes of the request buffer
  @param DataLen   Pointer whos value is to be filled with the byte count of
                   data copied to the output buffer

  @return EFI_SUCCESS if config value is successfully copied, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdGetConfig (
  VOID      *Buffer,
  UINT32    ReqLen,
  UINT32    *DataLen
  )
{
  EFI_STATUS    Status = EFI_DEVICE_ERROR;

  DEBUG ((DEBUG_INFO, "UsbdGetConfig()\n"));

  if (ReqLen >= 1) { /* length of data expected must be 1 */
    if (mDrvObj.ActiveConfigObj != NULL) { /* assure we have a config active */
      *DataLen = 1; /* one byte for ConfigurationValue */
      *(UINT8*)Buffer = mDrvObj.ActiveConfigObj->ConfigDesc->ConfigurationValue;

      Status = EFI_SUCCESS;
    } else {
      DEBUG ((DEBUG_INFO, "UsbdGetConfig() - No active configuration available\n"));
    }
  } else {
    DEBUG ((DEBUG_INFO, "UsbdGetConfig() - Invalid data length\n"));
  }

  return Status;
}


/**
  Returns the requested string descriptor if it exists

  @param Buffer    Pointer to destination buffer to copy descriptor data to
  @param DescIndex the index of the descriptor to return
  @param LangId    the target language ID
  @param ReqLen    the length in bytes of the request buffer
  @param DataLen   Pointer whos value is to be filled with the byte count of
                   data copied to the output buffer

  @return EFI_SUCCESS if descritor successfully copied, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdGetStringDesc (
  VOID      *Buffer,
  UINT8     DescIndex,
  UINT16    LangId,
  UINT32    ReqLen,
  UINT32    *DataLen
  )
{
  EFI_STATUS             Status = EFI_DEVICE_ERROR;
  UINT32                 Length = 0;
  USB_STRING_DESCRIPTOR  *StringDesc;
  UINT8                  Index = 0;
  UINT8                  StrLangEntries = 0;
  BOOLEAN                StrLangFound = FALSE;

  DEBUG ((DEBUG_INFO, "UsbdGetStringDesc: Index: 0x%x, LangId: 0x%x, ReqLen: 0x%x\n", DescIndex, LangId, ReqLen));

  /* index zero of the string table contains the supported language codes */
  if (DescIndex == 0) {
    StringDesc = (mDrvObj.UsbdDevObj->StringTable);
    Length = MIN (ReqLen, StringDesc->Length);
    CopyMem (Buffer, StringDesc, Length);
    *DataLen = Length;
    Status = EFI_SUCCESS;
  } else {

    /*
     * Verify the requested language ID is supported. String descriptor Zero
     * (First entry in the string table) is expected to contain the language list.
     * The requested language ID is specified in the Index member of the request.
     */
    StringDesc = mDrvObj.UsbdDevObj->StringTable; /* get language string descriptor */
    StrLangEntries = ((StringDesc->Length - 2) >> 1);
    DEBUG ((DEBUG_INFO, "StrLangEntries=%x\n", StrLangEntries));

    DEBUG ((DEBUG_INFO, "Looking LangID: \n"));

    for (Index = 0; Index < StrLangEntries; Index++) {
      DEBUG ((DEBUG_INFO, "LangID [%x]= %x\n", Index, StringDesc->LangID [Index]));

      if (StringDesc->LangID [Index] == LangId) {
        DEBUG ((DEBUG_INFO, "Found it\n"));
        StrLangFound = TRUE;
      }
    }



    /* If we found a matching language, attempt to get the string index requested */
    if (StrLangFound == TRUE) {
      DEBUG ((DEBUG_INFO, "UsbdGetStringDesc: StrLangFound=Found, DescIndex=%x, StrTblEntries=%x\n", DescIndex, mDrvObj.UsbdDevObj->StrTblEntries));

      if (DescIndex < mDrvObj.UsbdDevObj->StrTblEntries) {
        /* get the string descriptor for the requested index */
        StringDesc = (mDrvObj.UsbdDevObj->StringTable + DescIndex);

        Length = MIN (ReqLen, StringDesc->Length);
        DEBUG ((DEBUG_INFO, "ReqLen=%x, StringLength=%x, Length=%x\n", ReqLen, StringDesc->Length, Length));

        CopyMem (Buffer, StringDesc, Length);
        *DataLen = Length;
        Status = EFI_SUCCESS;
      } else {
        DEBUG ((DEBUG_INFO, "UsbdGetStringDesc: Invalid String index in USB_REQ_GET_DESCRIPTOR request\n"));
      }
    } else {
      DEBUG ((DEBUG_INFO, "UsbdGetStringDesc: Unsupported String Language ID for USB_REQ_GET_DESCRIPTOR request\n"));
    }
  }

  if (Status == EFI_SUCCESS) {
    PrintStringDescriptor (StringDesc);
  }
  return Status;
}


#ifdef SUPPORT_SUPER_SPEED
/**
  Returns the configuration descriptor for this device. The data
  buffer returned will also contain all downstream interface and
  endpoint buffers.

  @param Buffer    Pointer to destination buffer to copy descriptor data to
  @param ReqLen    the length in bytes of the request buffer
  @param DataLen   Pointer whos value is to be filled with the byte count of
                   data copied to the output buffer

  @return EFI_SUCCESS if descritor successfully copied, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdGetBOSDesc (
  IN VOID      *Buffer,
  IN UINT32    ReqLen,
  IN UINT32    *DataLen
  )
{
  EFI_USB_BOS_DESCRIPTOR  *BosDesc = 0;
  UINT32                  Length = 0;

  DEBUG ((DEBUG_INFO, "UsbdGetBOSDesc()\n"));

  BosDesc = mDrvObj.UsbdDevObj->BosDesc;
  Length = MIN (ReqLen, mDrvObj.UsbdDevObj->BosDesc->TotalLength);

  CopyMem(Buffer, BosDesc, Length);
  *DataLen = Length;

  PrintBOSDescriptor (BosDesc);

  return EFI_SUCCESS;
}
#endif

/**
  Returns the current status for Device/Interface/Endpoint

  @param Buffer    Pointer to destination buffer to copy descriptor data to
  @param ReqType   The type of status to get
  @param ReqLen    the length in bytes of the request buffer
  @param DataLen   Pointer whos value is to be filled with the byte count of
                   data copied to the output buffer

  @return EFI_SUCCESS if status successfully copied, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdGetStatus (
  VOID      *Buffer,
  UINT8     ReqType,
  UINT32    ReqLen,
  UINT32    *DataLen
  )
{
  EFI_STATUS  Status = EFI_DEVICE_ERROR;

  DEBUG ((DEBUG_INFO, "UsbdGetStatus()\n"));

  if (ReqLen >= 2) { /* length of data must be at least 2 bytes */
    switch (ReqType & USB_TARGET_MASK) {
      case USB_TARGET_DEVICE:
        *DataLen = 2; /* two byte for status */
        *(UINT16*)Buffer = USB_STATUS_SELFPOWERED;
        Status = EFI_SUCCESS;
        break;

      case USB_TARGET_INTERFACE:
        /* No implementation needed at this time */
        break;

      case USB_TARGET_ENDPOINT:
        /* No implementation needed at this time */
        /* Should specify if endpoint is halted. Implement as necessary. */
        break;

      case USB_TARGET_OTHER:
        /* No implementation needed at this time */
        break;

      default:
        break;
    }
  } else {
    DEBUG ((DEBUG_INFO, "UsbdGetStatus() - Invalid data length\n"));
  }

  return Status;
}


/**
  Sets the address of the device

  @param address   the address value to set

  @return EFI_SUCCESS if address was set, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdSetAddress (
  UINT8    Address
  )
{
  EFI_STATUS  Status = EFI_DEVICE_ERROR;

  DEBUG ((DEBUG_INFO, "UsbdSetAddress: setting address: 0x%x\n", Address));

  if (Address <= 0x7F) { /* address must not be > 127 */
    mDrvObj.Address = Address;

    /* Configure Address in the XDCI */
    Status = usb_device_set_address (mDrvObj.XdciDrvObj, mDrvObj.Address);
    if (!EFI_ERROR (Status)) {
      mDrvObj.State = UsbDevStateAddress;
    } else {
      DEBUG ((DEBUG_INFO, "UsbdSetAddress: Failed to set address in XDCI\n"));
    }
  } else {
    DEBUG ((DEBUG_INFO, "UsbdSetAddress: Invalid address: 0x%x\n", Address));
  }

  return Status;
}


/**
  Handles Setup device requests. Standard requests are immediately
  handled here, and any Class/Vendor specific requests are forwarded
  to the class driver

  @param CtrlRequest  Pointer to a device request

  @return EFI_SUCCESS if request successfully handled, FALSE otherwise

**/
EFI_STATUS
UsbdSetupHdlr (
  IN EFI_USB_DEVICE_REQUEST    *CtrlRequest
  )
{
  EFI_STATUS              Status = EFI_DEVICE_ERROR;
  UINT8                   DescIndex = 0;
  USB_DEVICE_DESCRIPTOR   *DevDesc = 0;
  USB_EP_INFO             EpInfo;

  /* Initialize the IO object */
  mCtrlIoReq.IoInfo.Length = 0;

  DEBUG ((DEBUG_INFO, "UsbdSetupHdlr start\n"));
  PrintDeviceRequest (CtrlRequest);

  /* Handle Standard Device Requests */
  if ((CtrlRequest->RequestType & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD) {
    switch (CtrlRequest->Request) {
      case USB_REQ_GET_DESCRIPTOR:
        DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Host requests get descriptor\n"));
        if (CtrlRequest->RequestType == USB_RT_TX_DIR_D_TO_H) {
          DescIndex = (CtrlRequest->Value & 0xff); /* low byte is the index requested */
          switch (CtrlRequest->Value >> 8) { /* high byte contains request type */
            case USB_DESC_TYPE_DEVICE:
              DEBUG ((DEBUG_INFO, "Descriptor tyep: Device\n"));
              DevDesc = mDrvObj.UsbdDevObj->DeviceDesc;
              /* copy the data to the output buffer */
              mCtrlIoReq.IoInfo.Length = MIN (CtrlRequest->Length, DevDesc->Length);
              CopyMem (mCtrlIoReq.IoInfo.Buffer, DevDesc, mCtrlIoReq.IoInfo.Length);
              PrintDeviceDescriptor (DevDesc);
              break;

            case USB_DESC_TYPE_CONFIG:
              DEBUG ((DEBUG_INFO, "Descriptor tyep: Configuration\n"));
              Status = UsbdGetConfigDesc (
                         mCtrlIoReq.IoInfo.Buffer,
                         DescIndex,
                         CtrlRequest->Length,
                         &(mCtrlIoReq.IoInfo.Length)
                         );
              break;

            case USB_DESC_TYPE_STRING:
              DEBUG ((DEBUG_INFO, "Descriptor tyep: String\n"));
              Status = UsbdGetStringDesc (
                         mCtrlIoReq.IoInfo.Buffer,
                         DescIndex,
                         CtrlRequest->Index,
                         CtrlRequest->Length,
                         &(mCtrlIoReq.IoInfo.Length)
                         );
              break;

#ifdef SUPPORT_SUPER_SPEED
            case USB_DESC_TYPE_BOS:
              DEBUG ((DEBUG_INFO, "Descriptor tyep: BOS\n"));
              Status = UsbdGetBOSDesc (
                         mCtrlIoReq.IoInfo.Buffer,
                         CtrlRequest->Length,
                         &(mCtrlIoReq.IoInfo.Length)
                         );
              break;

            case USB_DESC_TYPE_SS_ENDPOINT_COMPANION:
              DEBUG ((DEBUG_INFO, "Descriptor tyep: Endpoint Companion\n"));
              break;
#endif

            default:
              DEBUG ((DEBUG_INFO, "Descriptor tyep: Unsupported, USB_REQ_GET_DESCRIPTOR request: 0x%x\n", (CtrlRequest->Value >> 8)));
              break;
          }
        } else {
          DEBUG ((DEBUG_INFO, "UsbdSetupHdlr() - Invalid direction for USB_REQ_GET_DESCRIPTOR request\n"));
        }
        break;

      case USB_REQ_GET_CONFIG:
        DEBUG ((DEBUG_INFO, "USB_REQ_GET_CONFIG\n"));
        if (CtrlRequest->RequestType == USB_RT_TX_DIR_D_TO_H) {
          Status = UsbdGetConfig (mCtrlIoReq.IoInfo.Buffer, CtrlRequest->Length, &(mCtrlIoReq.IoInfo.Length));
        } else {
          DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Invalid direction for USB_REQ_GET_CONFIG request\n"));
        }
        break;

      case USB_REQ_SET_CONFIG:
        DEBUG ((DEBUG_INFO, "USB_REQ_SET_CONFIG\n"));
        if (CtrlRequest->RequestType == USB_RT_TX_DIR_H_TO_D) {
          Status = UsbdSetConfig ((UINT8)CtrlRequest->Value);
        } else {
          DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Invalid direction for USB_REQ_SET_CONFIG request\n"));
        }
        break;

      case USB_REQ_SET_ADDRESS:
        DEBUG ((DEBUG_INFO, "USB_REQ_SET_ADDRESS\n"));
        if (CtrlRequest->RequestType == USB_RT_TX_DIR_H_TO_D) {
          Status = UsbdSetAddress ((UINT8)CtrlRequest->Value);
        } else {
          DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Invalid direction for USB_REQ_SET_ADDRESS request\n"));
        }
        break;

      case USB_REQ_GET_STATUS:
        DEBUG ((DEBUG_INFO, "USB_REQ_GET_STATUS\n"));
        if (CtrlRequest->RequestType & USB_RT_TX_DIR_D_TO_H) {
          Status = UsbdGetStatus (mCtrlIoReq.IoInfo.Buffer, CtrlRequest->RequestType, CtrlRequest->Length, &(mCtrlIoReq.IoInfo.Length));
        } else {
          DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Invalid direction for USB_REQ_GET_STATUS request\n"));
        }
        break;

      case USB_REQ_CLEAR_FEATURE:
        DEBUG ((DEBUG_INFO, "USB_REQ_CLEAR_FEATURE\n"));
        if (CtrlRequest->Value == USB_FEATURE_ENDPOINT_HALT) {
          if ((CtrlRequest->Index & USB_RT_TX_DIR_MASK) == USB_RT_TX_DIR_H_TO_D) {
            EpInfo.ep_dir = UsbEpDirOut;
            EpInfo.ep_num = CtrlRequest->Index;
          } else {
            EpInfo.ep_dir = UsbEpDirIn;
            EpInfo.ep_num = CtrlRequest->Index & (~USB_RT_TX_DIR_MASK);
          }
          usb_device_ep_clear_stall (mDrvObj.XdciDrvObj, &EpInfo);
        }
        break;

      case USB_REQ_SET_FEATURE:
      case USB_REQ_SET_DESCRIPTOR:
      case USB_REQ_GET_INTERFACE:
      case USB_REQ_SET_INTERFACE:
      case USB_REQ_SYNCH_FRAME:
      default:
        DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Unsupported Standard Request: 0x%x\n", CtrlRequest->Request));
        break;
    }
  } else { /* This is not a Standard request, it specifies Class/Vendor handling */
    /* Forward request to class driver */
    DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Class/Vendor Request\n"));
    if (mDrvObj.UsbdDevObj->SetupCallback != NULL) {
      mDrvObj.UsbdDevObj->SetupCallback (CtrlRequest, &(mCtrlIoReq.IoInfo));
    }
  }

  DEBUG ((DEBUG_INFO, "mCtrlIoReq.IoInfo.Length = 0x%x\n", mCtrlIoReq.IoInfo.Length));

  /* Transfer data according to request if necessary */
  if (mCtrlIoReq.IoInfo.Length> 0) {
    Status = UsbdEpTxData (mDrvObj.XdciDrvObj, &mCtrlIoReq);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Failed to TX data\n"));
    }
  } else {
    /* If we are not responding with data, send control status */
    Status = usb_device_ep0_tx_status (mDrvObj.XdciDrvObj);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_INFO, "UsbdSetupHdlr: Failed to Tx Ep0 Status\n"));
    }
  }

  return Status;
}


/**
  Handles Connection done events. Sets the device address to zero.

  @return EFI_SUCCESS if able to set the address, EFI_DEVICE_ERROR otherwise

**/
EFI_STATUS
UsbdConnDoneHdlr (
  VOID
  )
{
  EFI_STATUS    Status = EFI_DEVICE_ERROR;

  DEBUG ((DEBUG_INFO, "UsbdConnDoneHdlr()\n"));

  /* reset device address to 0 */
  Status = usb_device_set_address (mDrvObj.XdciDrvObj, 0x0);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "UsbdConnDoneHdlr() - Failed to set address in XDCI\n"));
  }

  /* set the device state to attached/connected */
  mDrvObj.State = UsbDevStateAttached;

  return Status;
}


/**
  Handles transmit/receive completion events. Directly handles
  control endpoint events and forwards class/vendor specific events
  to the class drivers.

  @param   XferInfo   Pointer to xfer structure

  @return

**/
VOID
UsbdXferDoneHdlr (
  IN EFI_USB_DEVICE_XFER_INFO    *XferInfo
  )
{
  //
  // If this is a non-control transfer complete, notify the class driver
  //
  if (XferInfo->EndpointNum > 0) {
    if (mDrvObj.UsbdDevObj->DataCallback != NULL) {
      mDrvObj.UsbdDevObj->DataCallback (XferInfo);
    }
  }

  return;
}


/**
  Binds a USB class driver with this USB device driver core.
  After calling this routine, the driver is ready to begin
  USB processing.

  @param  UsbdDevObj   Pointer to a usbd device object which contains
                       all relevant information for the class driver device

  @return TRUE if binding was successful, FALSE otherwise

**/
EFI_STATUS
EFIAPI
UsbDeviceBind (
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This,
  IN USB_DEVICE_OBJ                             *UsbdDevObj
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  /* allocate Tx buffer */
  mCtrlIoReq.IoInfo.Buffer = AllocateZeroPool (USB_EPO_MAX_PKT_SIZE_ALL);
  if (mCtrlIoReq.IoInfo.Buffer != NULL) {
    mDrvObj.UsbdDevObj = UsbdDevObj;
    mDrvObj.ActiveConfigObj = NULL;
    mDrvObj.Address = 0;
    mDrvObj.State = UsbDevStateInit;
  } else {
    DEBUG ((DEBUG_INFO, "UsbDeviceBind() - Failed to allocate IO Buffer\n"));
    Status = EFI_DEVICE_ERROR;
  }

  return Status;
}


/**
  Unbinds the USB class driver from this USB device driver core.

  @return TRUE if successful, FALSE otherwise

**/
EFI_STATUS
EFIAPI
UsbDeviceUnbind (
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This
  )
{
  mDrvObj.UsbdDevObj = NULL;
  mDrvObj.ActiveConfigObj = NULL;
  mDrvObj.Address = 0;
  mDrvObj.State = UsbDevStateOff;
  mDrvObj.XdciInitialized = FALSE;

  /* release allocated buffer data */
  if (mCtrlIoReq.IoInfo.Buffer) {
    FreePool (mCtrlIoReq.IoInfo.Buffer);
  }

  return EFI_SUCCESS;
}


/**
  Performs continual USB device event processing until a cancel
  event occurs

  @param   TimeoutMs   Connection timeout in ms. If 0, waits forever.
  @return  TRUE if run executed normally, FALSE if error ocurred

**/
EFI_STATUS
EFIAPI
UsbDeviceRun (
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This,
  IN UINT32                                     TimeoutMs
  )
{
  EFI_STATUS              Status = EFI_DEVICE_ERROR;
  USB_XDCI_DEV_CONTEXT    *XdciDevContext;

  XdciDevContext = USBUSBD_CONTEXT_FROM_PROTOCOL (This);

  /* can only run if XDCI is initialized */
  if ((mDrvObj.XdciInitialized == TRUE)) {

    if ((mDrvObj.State == UsbDevStateConfigured) && (XdciDevContext->XdciPollTimer == NULL)) {
      Status = gBS->CreateEvent (
                      EVT_TIMER | EVT_NOTIFY_SIGNAL,
                      TPL_NOTIFY,
                      UsbdMonitorEvents,
                      XdciDevContext,
                      &XdciDevContext->XdciPollTimer
                      );
      if (!EFI_ERROR (Status)) {
        Status = gBS->SetTimer (XdciDevContext->XdciPollTimer, TimerPeriodic, EFI_TIMER_PERIOD_MILLISECONDS (20));
        DEBUG ((EFI_D_ERROR, "UsbDeviceRun Create Event\n"));
      }
    }

    mXdciRun = TRUE; /* set the run flag to active */
    Status = EFI_SUCCESS;

    /* start the Event processing loop */
    /* DEBUG ((DEBUG_INFO, "UsbDeviceRun() - Starting event processing...\n")); */
    while (TRUE) {
      if (XdciDevContext->XdciPollTimer == NULL) {
        if (usb_device_isr_routine (mDrvObj.XdciDrvObj) != EFI_SUCCESS) {
          DEBUG ((DEBUG_INFO, "UsbDeviceRun() - Failed to execute event ISR\n"));
        }
      }

      /* Check if a run cancel request exists, if so exit processing loop */
      if (mXdciRun == FALSE) {
        if (XdciDevContext->XdciPollTimer != NULL) {
          DEBUG ((EFI_D_ERROR, "UsbDeviceRun close Event\n"));
          gBS->SetTimer (XdciDevContext->XdciPollTimer, TimerCancel, 0);
          gBS->CloseEvent (XdciDevContext->XdciPollTimer);
          XdciDevContext->XdciPollTimer = NULL;
        }
        Status = EFI_SUCCESS;
        DEBUG ((DEBUG_INFO, "UsbDeviceRun() - processing was cancelled\n"));
        break;
      }

      /* check for timeout */
      if (TimeoutMs == 0)
        return EFI_TIMEOUT;
      gBS->Stall (50);
      TimeoutMs--;
    }
  }

  return Status;
}


/**
  Sets a flag to stop the running device processing loop

  @return TRUE always

**/
EFI_STATUS
EFIAPI
UsbDeviceStop (
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This
  )
{
  mXdciRun = FALSE; /* set run flag to FALSE to stop processing */
  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
UsbDeviceInitXdci (
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This
  )
{
  EFI_STATUS            Status = EFI_DEVICE_ERROR;
  USB_XDCI_DEV_CONTEXT  *XdciDevContext;

  XdciDevContext = USBUSBD_CONTEXT_FROM_PROTOCOL (This);

  PlatformSpecificInit ();

  if (mDrvObj.XdciInitialized == FALSE) {
    if (XdciDevContext->XdciMmioBarAddr != 0) {

      /* Initialize device controller driver */
      DEBUG ((DEBUG_INFO, "UsbDeviceInitXdci() - Initializing Controller...\n"));

      /* Initialize the device controller interface */
      if (UsbdInit ((UINT32)XdciDevContext->XdciMmioBarAddr, &mDrvObj.XdciDrvObj) == EFI_SUCCESS) {

        /* Setup callbacks */
        if (UsbdRegisterCallbacks (mDrvObj.XdciDrvObj) == EFI_SUCCESS) {

          mDrvObj.XdciInitialized = TRUE;
          Status = EFI_SUCCESS;

          DEBUG ((DEBUG_INFO, "UsbDeviceInitXdci() - Controller initialization complete\n"));
        } else {
          DEBUG ((DEBUG_INFO, "UsbDeviceInitXdci() - Failed to register UDCI callbacks\n"));
        }
      } else {
        DEBUG ((DEBUG_INFO, "UsbDeviceInitXdci() - Failed to initialize UDCI\n"));
      }
    } else {
      DEBUG ((DEBUG_INFO, "UsbDeviceInitXdci() - XDCI MMIO BAR not set\n"));
    }
  } else {
    DEBUG ((DEBUG_INFO, "UsbDeviceInitXdci() - XDCI already initialized\n"));
    Status = EFI_ALREADY_STARTED;
  }

  return Status;
}


EFI_STATUS
EFIAPI
UsbDeviceConnect(
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This
  )
{
  EFI_STATUS  Status = EFI_DEVICE_ERROR;

  DEBUG ((DEBUG_INFO,  "UsbDeviceConnect \n"));
  if (usb_device_connect (mDrvObj.XdciDrvObj) == EFI_SUCCESS) {
    Status = EFI_SUCCESS;
  }
  return Status;
}


EFI_STATUS
EFIAPI
UsbDeviceDisConnect (
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This
  )
{
  EFI_STATUS  Status = EFI_DEVICE_ERROR;

  DEBUG ((DEBUG_INFO,  "UsbDeviceDisConnect \n"));
  if (usb_device_disconnect (mDrvObj.XdciDrvObj) == EFI_SUCCESS) {
    mDrvObj.State = UsbDevStateInit;
    Status = EFI_SUCCESS;
  }

  return Status;
}


EFI_STATUS
EFIAPI
UsbDeviceEpTxData(
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This,
  IN USB_DEVICE_IO_REQ                          *IoRequest
  )
{
  EFI_STATUS  Status;

  Status = UsbdEpTxData (mDrvObj.XdciDrvObj, IoRequest);
  return Status;
}


EFI_STATUS
EFIAPI
UsbDeviceEpRxData(
  IN EFI_USB_DEVICE_MODE_PROTOCOL               *This,
  IN USB_DEVICE_IO_REQ                          *IoRequest
  )
{
  EFI_STATUS  Status;

  Status = UsbdEpRxData (mDrvObj.XdciDrvObj, IoRequest);
  return Status;
}


//
// The Runtime UsbDeviceMode Protocol instance produced by this driver
//
EFI_USB_DEVICE_MODE_PROTOCOL  mUsbDeviceModeProtocol = {
  UsbDeviceInitXdci,
  UsbDeviceConnect,
  UsbDeviceDisConnect,
  UsbDeviceEpTxData,
  UsbDeviceEpRxData,
  UsbDeviceBind,
  UsbDeviceUnbind,
  UsbDeviceRun,
  UsbDeviceStop
};
